#include "global.h"
#include "memory.h"
#include "print.h"
#include "stdint.h"
#include "string.h"
#include "thread.h"
#include "tss.h"

static struct tss_t tss;

/**
 * @brief Create a GDT descriptor.
 *
 * @param base_addr Pointer to the descriptor base address.
 * @param limit     Limit of the descriptor.
 * @param attr_low  Low attribute byte, low 4 bits must set to zero.
 * @param attr_high High attribute byte of the descriptor.
 *
 * @return The GDT descriptor created.
 */
struct gdt_desc make_gdt_desc(uint32_t* base_addr, uint32_t limit,
                              uint8_t attr_low, uint8_t attr_high);

void tss_init()
{
  put_str("init tss start\r\n");

  memset(&tss, (uint32_t)sizeof(struct tss_t), (uint8_t)0);
  tss.ss0 = SELECTOR_K_STACK;
  tss.io_bitmap_offset = sizeof(struct tss_t); // no io bitmap

  // tss segment descriptor
  *((struct gdt_desc*)0xc0000923) = make_gdt_desc(
      (uint32_t*)&tss, sizeof(struct tss_t) - 1, TSS_ATTR_LOW, TSS_ATTR_HIGH);

  // user code segment descriptor
  *((struct gdt_desc*)0xc000092b) = make_gdt_desc(
      (uint32_t*)0, 0xfffff, GDT_UCODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH);

  // user data segment descriptor
  *((struct gdt_desc*)0xc0000933) = make_gdt_desc(
      (uint32_t*)0, 0xfffff, GDT_UDATA_ATTR_LOW_DPL3, GDT_ATTR_HIGH);

  uint64_t gdt_operand = ((8 * 7 - 1) | ((uint64_t)0xc0000903 << 16));

  asm volatile("lgdt %0" ::"m"(gdt_operand));
  asm volatile("ltr %w0" ::"r"(SELECTOR_K_TSS));

  put_str("tss_init and ltr done\r\n");
}

void tss_esp_update(struct task_struct* pthread)
{
  tss.esp0 = (uint32_t*)((uint32_t)pthread + PG_SIZE);
}

struct gdt_desc make_gdt_desc(uint32_t* base_addr, uint32_t limit,
                              uint8_t attr_low, uint8_t attr_high)
{
  uint32_t        desc_base = (uint32_t)base_addr;
  struct gdt_desc desc;
  desc.limit_low_word = (uint16_t)(limit & 0xffff);
  desc.base_low_word = (uint16_t)(desc_base & 0xffff);
  desc.base_mid_byte = (uint8_t)((desc_base & 0xff0000) >> 16);
  desc.attr_low_byte = attr_low;
  desc.limit_high_attr_high = attr_high + (uint8_t)((limit & 0xf0000) >> 16);
  desc.base_high_byte = (uint8_t)((desc_base & 0xff000000) >> 24);

  return desc;
}
